//
// Created by gastonfeng on 2022/11/3.
//
#ifdef ARDUINO_ARCH_STM32

#include "Rtos.h"
#include "hwboard.h"
#include "mFirmata.h"
#include "pb_encode.h"
#include "ulp.h"
#include <kSerial.h>
#include <stm32yyxx_ll_usart.h>
#include <uart.h>


#ifndef THIS_IS_BOOTLOADER

int kSerial::diag() {
  //    serial_s ser = _serial;
  //    logger.debug("CR1 = 0x%x\n",
  //                 (volatile long unsigned int) ser.uart->CR1); /*!< USART
  //                 Control register 1,                 Address offset: 0x00 */
  //    logger.debug("CR2 = 0x%x\n",
  //                 (volatile long unsigned int) ser.uart->CR2); /*!< USART
  //                 Control register 2,                 Address offset: 0x04 */
  //    logger.debug("CR3 = 0x%x\n",
  //                 (volatile long unsigned int) ser.uart->CR3); /*!< USART
  //                 Control register 3,                 Address offset: 0x08 */
  //    logger.debug("BRR = 0x%x\n",
  //                 (volatile long unsigned int) ser.uart->BRR); /*!< USART
  //                 Baud rate register,                 Address offset: 0x0C */
  //    logger.debug("GTPR = 0x%x\n",
  //                 (volatile long unsigned int) ser.uart->GTPR); /*!< USART
  //                 Guard time and prescaler register,  Address offset: 0x10 */
  return 0;
}

int kSerial::end() {
  flag &= ~IS_OPEN;
  flush();
  uart_deinit(&_serial);
  pinMode(_serial.pin_tx, INPUT_ANALOG);
  pinMode(_serial.pin_rx, INPUT_ANALOG);

  if (tmr_rx) {
    Rtos::timer_destroy(tmr_rx);
    tmr_rx = nullptr;
  }
  Rtos::mutex_destroy(rx_lock);
  Rtos::mutex_destroy(tx_lock);
  free(_serial.tx_buff);
  free(_serial.rx_buff);
  return 0;
}

#endif
using SerialConfig = u8;

int kSerial::begin(unsigned int baud, u8 config, int rx_buff_sz,
                   int tx_buff_sz) {
  data.baud = baud;
  data.config = config;
  data.tx_buf_size = tx_buff_sz;
  data.rx_buf_size = rx_buff_sz;
  data.rx_timeout = 0;
  _serial.pin_cts = NC;
  _serial.pin_rts = NC;
  _serial.rx_buff = nullptr;
  _serial.rx_head = 0;
  _serial.rx_tail = 0;
  _serial.tx_buff = (uint8_t *) malloc(tx_buff_sz);
  _serial.rx_buff = (uint8_t *) malloc(rx_buff_sz);
  _serial.tx_head = 0;
  _serial.tx_tail = 0;
#ifdef USE_FREERTOS
  tx_lock = Rtos::Create_Mutex();
  rx_lock = Rtos::Create_Mutex();
  osSemaphoreWait(rx_lock, 0);
  if (data.rx_timeout > 0)
    tmr_rx = Rtos::Create_Timer("serial_tmr", (void *) &_serial_tmr_cb, this,
                                data.rx_timeout);
#else
#endif
  uint32_t databits = 0;
  uint32_t stopbits = 0;
  uint32_t parity = 0;
  // Manage databits
  switch (config & 0x07) {
    case 0x02:
      databits = 6;
          break;
    case 0x04:
      databits = 7;
          break;
    case 0x06:
      databits = 8;
          break;
    default:
      //        data.state = pb_event_DATABITS_ERROR;
      break;
  }

  if (0x30 == (config & 0x30)) {
    parity = UART_PARITY_ODD;
    databits++;
  } else if (0x20 == (config & 0x20)) {
    parity = UART_PARITY_EVEN;
    databits++;
  } else {
    parity = UART_PARITY_NONE;
  }

  if (0x08 == (config & 0x08)) {
    stopbits = UART_STOPBITS_2;
  } else {
    stopbits = UART_STOPBITS_1;
  }

  switch (databits) {
#ifdef UART_WORDLENGTH_7B
    case 7:
      databits = UART_WORDLENGTH_7B;
          break;
#endif
    case 9:
      databits = UART_WORDLENGTH_9B;
          break;
    default:
    case 8:
      databits = UART_WORDLENGTH_8B;
          break;
  }

  uart_init(&_serial, (uint32_t) baud, databits, parity, stopbits);

#if defined(STM32H750xx) || defined(STM32L4xx)
  if (flag & TX_INV) {
    LL_USART_Disable(_serial.uart);
    LL_USART_SetTXPinLevel(_serial.uart, LL_USART_TXPIN_LEVEL_INVERTED);
    LL_USART_Enable(_serial.uart);
  }
  if (flag & RX_INV) {
    LL_USART_Disable(_serial.uart);
    LL_USART_SetRXPinLevel(_serial.uart, LL_USART_RXPIN_LEVEL_INVERTED);
    LL_USART_Enable(_serial.uart);
  }
  if (flag & TX_RX_SWAP) {
    LL_USART_Disable(_serial.uart);
    LL_USART_SetTXRXSwap(_serial.uart, LL_USART_TXRX_SWAPPED);
    LL_USART_Enable(_serial.uart);
  }
#endif
  enableHalfDuplexRx();
  uart_attach_rx_callback(&_serial, _rx_complete_irq);
  flag |= IS_OPEN;
  return 0;
}

void kSerial::_rx_complete_irq(serial_t *obj) {
  unsigned char c;
  if (uart_getc(obj, &c) == 0) {
    kSerial *k = (kSerial *) ((char *) obj - offsetof(kSerial, _serial));
    k->data.rx_count++;
    obj->rx_buff[obj->rx_head] = c;
    obj->rx_head = (obj->rx_head + 1) % k->data.rx_buf_size;
#ifdef USE_FREERTOS
    if (k->data.rx_timeout > 0) {
      if (k->timer_is_running) {
        Rtos::tmr_reset_ISR(k->tmr_rx);
      } else {
        Rtos::timer_start_ISR(k->tmr_rx, k->data.rx_timeout);
        k->timer_is_running = true;
      }
    } else if (k->wait) {

      Rtos::mutex_unlock(k->rx_lock);
    }
#endif
  }
}

bool kSerial::isHalfDuplex() const { return _serial.pin_rx == NC; }

void kSerial::enableHalfDuplexRx() {
  if (isHalfDuplex()) {
    // In half-duplex mode we have to wait for all TX characters to
    // be transmitted before we can receive data.
    flush();
    if (!_rx_enabled) {
      _rx_enabled = true;
      uart_enable_rx(&_serial);
    }
  }
}

int kSerial::available_wait(int delay) {
  // if (data.rx_timeout > 0)
  if (!available()) {
    wait = true;
    Rtos::mutex_lock(rx_lock, delay);
    wait = false;
  }
  // else
  // {
  //     uint32_t start = millis();
  //     while ((millis() - start) < delay)
  //     {
  //         if (available())
  //             break;
  //         Rtos::Delay(1);
  //     }
  // }
  return available();
}

int kSerial::availableForWrite() {
  tx_buffer_index_t head = _serial.tx_head;
  tx_buffer_index_t tail = _serial.tx_tail;

  if (head >= tail) {
    return data.tx_buf_size - 1 - head + tail;
  }
  return tail - head - 1;
}

int kSerial::_tx_complete_irq(serial_t *obj) {
  int remaining_data;
  // previous HAL transfer is finished, move tail pointer accordingly
  kSerial *k = (kSerial *) ((char *) obj - offsetof(kSerial, _serial));
  obj->tx_tail = (obj->tx_tail + obj->tx_size) % k->data.tx_buf_size;
  k->data.tx_count += obj->tx_size;

  // If buffer is not empty (head != tail), send remaining data
  if (obj->tx_head != obj->tx_tail) {
    remaining_data = (k->data.tx_buf_size + obj->tx_head - obj->tx_tail) %
                     k->data.tx_buf_size;
    // Limit the next transmission to the buffer end
    // because HAL is not able to manage rollover
    obj->tx_size =
            min(remaining_data, (int) (k->data.tx_buf_size - obj->tx_tail));
    uart_attach_tx_callback(obj, _tx_complete_irq, obj->tx_size);
#if (configUSE_TICKLESS_IDLE == 2)
    vUlpOnPeripheralsActiveFromISR(ulpPERIPHERAL_USART);
#endif
    return -1;
  } else {
#if (configUSE_TICKLESS_IDLE == 2)
    vUlpOnPeripheralsInactiveFromISR(ulpPERIPHERAL_USART);
#endif
    k->end_transmit();
    // assert(k->data.tx_count == k->write_count);
    Rtos::mutex_unlock(k->tx_lock);
  }
  return 0;
}

void kSerial::set_pin(int tx_pin, int rx_pin, int de_pin) {
  _serial.pin_tx = digitalPinToPinName(tx_pin);
  _serial.pin_rx = digitalPinToPinName(rx_pin);
  if (de_pin != -1) {
    pin_de = digitalPinToPinName(de_pin);
    pinMode(de_pin, OUTPUT);
    digitalWrite(de_pin, LOW);
    data.pin_485 = true;
  }
}

void kSerial::set_pin(PinName tx_pin, PinName rx_pin) {
  _serial.pin_tx = tx_pin;
  _serial.pin_rx = rx_pin;
}

int kSerial::write(const u8 *buffer, int size) {
  int size_intermediate;
  int ret = size;
  begin_transmit();
  if (isHalfDuplex()) {
    if (_rx_enabled) {
      _rx_enabled = false;
      uart_enable_tx(&_serial);
    }
  }
  _serial.tx_tail = 0;
  _serial.tx_head = 0;

  memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size);
  _serial.tx_head = size;
  write_count += size;
  // Data are copied to buffer, move head pointer accordingly

  // Transfer data with HAL only is there is no TX data transfer ongoing
  // otherwise, data transfer will be done asynchronously from callback
  if (!serial_tx_active(&_serial)) {
    // note: tx_size correspond to size of HAL data transfer,
    // not the total amount of data in the buffer.
    // To compute size of data in buffer compare head and tail
    _serial.tx_size = size;
    uart_attach_tx_callback(&_serial, _tx_complete_irq, size);
#if (configUSE_TICKLESS_IDLE == 2)
    vUlpOnPeripheralsActive(ulpPERIPHERAL_USART);
#endif
    Rtos::mutex_lock(tx_lock, size * 2);
  }

  return ret;
}

int kSerial::peek() {
  if (_serial.rx_head == _serial.rx_tail) {
    return -1;
  } else {
    return _serial.rx_buff[_serial.rx_tail];
  }
}

int kSerial::write(uint8_t ch) {
#ifdef HAL_IRDA_MODULE_ENABLED
  if (_sd_pin)
    digitalWrite(_sd_pin, LOW);
#endif
  uint8_t buff = ch;
  return write(&buff, 1);
}

int kSerial::available() {
  return (_serial.rx_head - _serial.rx_tail + data.rx_buf_size) %
         data.rx_buf_size;
}

int kSerial::read() {
  if (_serial.rx_head == _serial.rx_tail) {
    return -1;
  } else {
    uint8_t c = _serial.rx_buff[_serial.rx_tail];
    _serial.rx_tail = (_serial.rx_tail + 1) % data.rx_buf_size;
    return c;
  }
}

int kSerial::read_wait(int timeout) {
  int c = -1;
  if (available_wait(timeout)) {
    c = read();
  }
  return c;
}

void kSerial::flush() {
#ifdef HAL_IRDA_MODULE_ENABLED
  if (_sd_pin) {
    while (!LL_USART_IsActiveFlag_TXE(_serial.uart))
      ;
    digitalWrite(_sd_pin, HIGH);
  }
#endif

  // while ((_serial.tx_head != _serial.tx_tail)) {
  //     // nop, the interrupt handler will free up space for us
  // }
}

#ifndef THIS_IS_BOOTLOADER

void kSerial::set_low() {
  //    pinMode(serial->_serial.pin_tx, OUTPUT);
  //    digitalWrite(serial->_serial.pin_tx, LOW);
}

void kSerial::set_rx() { begin(data.baud, (SerialConfig) data.config); }

void kSerial::set_high(){
#ifdef ARDUINO_ARCH_STM32
//    pinMode(serial->_serial.pin_tx, OUTPUT);
//    digitalWrite(serial->_serial.pin_tx, HIGH);
#endif
}

#endif

u8 kSerial::data_bits() const {
  switch (data.config) {
    case SERIAL_8N1:
    case SERIAL_8N2:
    case SERIAL_8E1:
    case SERIAL_8E2:
    case SERIAL_8O1:
    case SERIAL_8O2:
      return 8;

    case SERIAL_7E1:
    case SERIAL_7E2:
    case SERIAL_7O1:
    case SERIAL_7O2:
      return 7;
    default:
      return 0;
  }
}

u8 kSerial::stop_bits() const {
  switch (data.config) {
    case SERIAL_8N1:
    case SERIAL_8E1:
    case SERIAL_8O1:
    case SERIAL_7E1:
    case SERIAL_7O1:
      return 1;
    case SERIAL_8N2:
    case SERIAL_8E2:
    case SERIAL_8O2:
    case SERIAL_7E2:
    case SERIAL_7O2:
      return 2;
    default:
      return 0;
  }
}

uint32_t kSerial::baud() const { return data.baud; }

u8 kSerial::parity() const {
  switch (data.config) {
    case SERIAL_8N1:
    case SERIAL_8N2:
      return 0;
    case SERIAL_8E1:
    case SERIAL_8E2:
    case SERIAL_7E1:
    case SERIAL_7E2:
      return 2;
    case SERIAL_8O1:
    case SERIAL_8O2:
    case SERIAL_7O1:
    case SERIAL_7O2:
      return 1;
    default:
      return 0;
  }
}

int kSerial::begin_transmit() {
  if (data.pin_485 && pin_de != -1) {
    pinMode(pin_de, OUTPUT);
    digitalWrite(pin_de, HIGH);
  }
  return 0;
}

int kSerial::end_transmit() {
  if (data.pin_485 && pin_de != -1 && _serial.tx_size == 0) {
    digitalWrite(pin_de, LOW);
  }
  return 0;
}

#ifndef THIS_IS_BOOTLOADER

void kSerial::configForLowPower() {
#if defined(HAL_PWR_MODULE_ENABLED) && \
    (defined(UART_IT_WUF) || defined(LPUART1_BASE))
  // Reconfigure properly Serial instance to use HSI as clock source
  end();
  uart_config_lowpower(&_serial);
  begin(data.baud, data.config);
#endif
}

void kSerial::get_info(mFirmata *mf, nStream *ns, byte cmd, uint16_t argc,
                       byte *argv) {
  int sinx = argv[0];
  if (sinx >= Serials_nrs) {
    byte msg[3];
    *(short *) msg = pb_event_RANGE_OV;
    msg[2] = sinx;
    mf->sendSysex(ns, cmd, 3, msg);
  }
  uint32_t len = ns->tx_max_size();
  byte *buffer = (byte *) malloc(len);
  pb_ostream_t ostream = pb_ostream_from_buffer(buffer, len);
  kSerial *s = get_serial(sinx);
  int ret = pb_encode(&ostream, pb_serial_fields, &s->data);
  if (!ret) {
    const char *error = PB_GET_ERROR(&ostream);
    logger.error("dir_buf pb_encode error: %s", error);
  }
  mf->sendSysex(ns, cmd, ostream.bytes_written, buffer);
  free(buffer);
}

#ifdef USE_FREERTOS

void kSerial::_serial_tmr_cb(void *pVoid) {
  auto tmr = (TimerHandle_t) pVoid;
  kSerial *serial = (kSerial *) pvTimerGetTimerID(tmr);
  serial->timer_is_running = false;
  Rtos::mutex_unlock(serial->rx_lock);
}

#endif
#endif

#endif
